package net.protocol.diameter.avp;

import net.protocol.common.coding.ByteMe;

import java.nio.ByteBuffer;

/**
 * <h3>AVP Header</h3>
 * <p/>
 * <pre>
 *     The fields in the AVP header MUST be sent in network byte order.  The
 *     format of the header is:
 *
 *      0                   1                   2                   3
 *      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *     |                           AVP Code                            |
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *     |V M P r r r r r|                  AVP Length                   |
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *     |                        Vendor-ID (opt)                        |
 *     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *     |    Data ...
 *     +-+-+-+-+-+-+-+-+
 * </pre>
 *
 * @author jinhongw@gmail.com
 */
class DiameterAVPHeader implements ByteMe {
    public final static int V_FLAGS_MASK = 0x80;
    public final static int M_FLAGS_MASK = 0x40;
    public final static int P_FLAGS_MASK = 0x20;

    private final static int MIN_LENGTH_NO_DATA = 8;
    private final static int MAX_LENGTH_NO_DATA = 12;

    private final static int DEFAULT_VENDOR_ID = AbstractAVP.DEFAULT_VENDOR_ID;

    private final int code;
    private byte flags = M_FLAGS_MASK;
    private final int length;
    private final int vendorId;

    /**
     * @param code       AVP Code
     * @param vendorId   Vendor-ID
     * @param dataLength AVP Data Length
     */
    public DiameterAVPHeader(int code, int vendorId, int dataLength) {
        this.code = code;
        if (vendorId != DEFAULT_VENDOR_ID) {
            this.length = MAX_LENGTH_NO_DATA + dataLength;
            
            this.vendorId = vendorId;
            setVendorId(true);
        } else {
        	 this.length = MIN_LENGTH_NO_DATA + dataLength;
        	 
            this.vendorId = DEFAULT_VENDOR_ID;
        }
    }

    /**
     * @param src The buffer from which bytes are to be retrieved
     */
    public DiameterAVPHeader(ByteBuffer src) {
        this.code = src.getInt();

        int vl = src.getInt();
        this.flags = (byte) (vl >> 24);
        this.length = ((vl << 8) >>> 8);

        if (isVendorId())
            this.vendorId = src.getInt();
        else
            this.vendorId = DEFAULT_VENDOR_ID;
    }

    @Override
    public ByteBuffer byteMe(ByteBuffer dst) {
        dst.putInt(this.code);

        int vl = (this.flags << 24) + this.length;
        dst.putInt(vl);

        if (isVendorId()) dst.putInt(this.vendorId);
        
        return dst;
    }

    /**
     * indicates whether the optional Vendor-ID field is present in the AVP header.
     *
     * @return a <code>boolean</code> indicating whether or not 'V' Bit is enabled.
     */
    public boolean isVendorId() {
        return (flags & V_FLAGS_MASK) != 0;
    }

    /**
     * indicates whether support of the AVP is required.
     *
     * @return a <code>boolean</code> indicating whether or not 'M' Bit is enabled.
     */
    public boolean isMandatory() {
        return (flags & M_FLAGS_MASK) != 0;
    }

    /**
     * indicates the need for encryption for end-to-end security.
     *
     * @return a <code>boolean</code> indicating whether or not 'P' Bit is enabled.
     */
    public boolean isProtected() {
        return (flags & P_FLAGS_MASK) != 0;
    }

    /**
     * The 'V' Bit Means Vendor Specific.
     *
     * @param flag <code>true</code> to set 'V' Bit,<code>false</code> to disable.
     */
    public void setVendorId(boolean flag) {
        if (flag)
            flags |= V_FLAGS_MASK;
        else
            flags &= ~V_FLAGS_MASK;
    }

    /**
     * The 'M' Bit if set means Mandatory.
     *
     * @param flag <code>true</code> to set 'M' Bit,<code>false</code> to disable.
     */
    public void setMandatory(boolean flag) {
        if (flag)
            flags |= M_FLAGS_MASK;
        else
            flags &= ~M_FLAGS_MASK;
    }

    /**
     * The 'P' bit if set means Protected.
     *
     * @param flag <code>true</code> to set 'P' Bit,<code>false</code> to disable.
     */
    public void setProtected(boolean flag) {
        if (flag)
            flags |= P_FLAGS_MASK;
        else
            flags &= ~P_FLAGS_MASK;
    }

    /**
     * @return AVP Code.
     */
    public int getCode() {
        return code;
    }

    /**
     * @return Vendor-ID (opt).
     */
    public int getVendorId() {
        return vendorId;
    }

    /**
     * AVPHeaderLength + AVPDataLength.
     *
     * @return AVP length including AVP Data.
     */
    public int getLength() {
        return length;
    }

    /**
     * @return AVP Header Length except AVP Data.
     */
    public int getHeaderLength() {
        if (isVendorId()) return MAX_LENGTH_NO_DATA;
        return MIN_LENGTH_NO_DATA;
    }

    /**
     * @return AVP Data Length.
     */
    public int getDataLength() {
        return getLength() - getHeaderLength();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        DiameterAVPHeader other = (DiameterAVPHeader) obj;
        if (code != other.code)
            return false;
        if (length != other.length)
            return false;
        if (flags != other.flags)
            return false;
        if (isVendorId()) {
            if (vendorId != other.vendorId)
                return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + code;
        result = prime * result + flags;
        result = prime * result + length;
        if (isVendorId())
            result = prime * result + vendorId;
        return result;
    }

    @Override
    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append(code).append(",");
        buf.append(isVendorId() ? "V" : "");
        buf.append(isMandatory() ? "M" : "");
        buf.append(isProtected() ? "P" : "");
        buf.append(",");
        if (isVendorId()) {
            buf.append("v=").append(vendorId);
            buf.append(",");
        }
        buf.append("l=").append(length);
        return buf.toString();
    }
}
